package core

import (
	"errors"
	"fmt"
	"reflect"
	"time"

	"gitee.com/knowgo/ego/core/function"
	"gitee.com/knowgo/ego/core/types"
	"gitee.com/knowgo/ego/egoc"
)

func (c *Core) parseService(
	groupType reflect.Type,
	method reflect.Method,
	rootArgumentIsRequired bool,
	fnGetTimeormat func(format egoc.TimeFormatter) (string, *time.Location),
) (*function.Service, error) {
	svc := &function.Service{
		// owner:  groupType,
		Method: method,
	}
	var methodName = fmt.Sprintf("%s\n%s.%s", groupType.PkgPath(), groupType.Name(), method.Name)
	var methodType = method.Type
	var err error
	svc.ReturnType, err = c.parseReturn(methodType, methodName)
	if err != nil {
		return nil, err
	}
	// 解析输入参数
	svc.Arguments, svc.ArgumentType, err = c.parseArguments(methodType, methodName, rootArgumentIsRequired, fnGetTimeormat)
	if err != nil {
		return nil, err
	}

	return svc, nil
}

func (c *Core) parseArguments(methodType reflect.Type, methodName string, rootArgumentIsRequired bool,
	fnGetTimeormat func(format egoc.TimeFormatter) (string, *time.Location)) ([]function.Argument, *function.ArgumentObject, error) {
	var loader *function.ServiceLoader
	count := methodType.NumIn()
	var hasInput = false
	var arguments []function.Argument
	var arg *function.ArgumentObject
	for n := 0; n < count; n++ {
		var argItem = methodType.In(n)
		var argIsPtr = argItem.Kind() == reflect.Ptr
		if argIsPtr {
			argItem = argItem.Elem()
		}
		if n == 0 {
			// 结构体
			loader = c.loaders[argItem]
			if loader == nil {
				loader = function.ParseServiceLoader(argItem, argIsPtr, c.injectors)
				c.loaders[argItem] = loader
			}
			arguments = append(arguments, loader)
			continue
		}
		if types.IsContextType(argItem) {
			// *ego.Context
			arguments = append(arguments, function.NewContextArg(argIsPtr))
			continue
		}
		// 判断是否注入类型
		var injector = c.injectors.Find(argItem)
		if injector != nil {
			// 是注入类型
			arguments = append(arguments, function.NewInjectArg(injector, argIsPtr))
			continue
		}
		// 不是注入类型
		if hasInput {
			// 只能有一个不是注入类型的参数
			return nil, nil, fmt.Errorf("ingore func too many arguments: %s", methodName)
		}

		// 此处必须使用 methodType.In(n) 作为参数
		input, err := function.ParseArgument(c.ArgumentTypes, methodType.In(n), methodName, c.log, fnGetTimeormat)
		if err != nil {
			return nil, nil, err
		}
		arg = input
		arguments = append(arguments, function.NewRequestArg(input.Info, c, rootArgumentIsRequired))
		hasInput = true
	}
	return arguments, arg, nil
}

func (c *Core) GetArgumentType(typeName string) *function.ArgumentTypeInfo {
	return c.ArgumentTypes[typeName]
}

func (c *Core) parseReturn(methodType reflect.Type, methodName string) (*function.Return, error) {
	count := methodType.NumOut()
	if count != 2 {
		return nil, errors.New("service func must has 2 returns")
	}
	// 两个返回值时，第二个必须是 error
	out := methodType.Out(1)
	if !types.IsErrorType(out) {
		// 不是 error
		return nil, types.NewErrFuncIsInvalidService("service func's second returns must be error")
	}
	out = methodType.Out(0)

	ret, err := function.ParseReturn(c.ReponseTypes, out, methodName, c.log)
	if err != nil {
		return nil, err
	}
	return ret, nil
}
